using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Threading;
using UnityEngine;
using VoxelEngine.Utils;

namespace VoxelEngine
{
	public class World
	{
		private const string WorldPersistentDirectoryRelativePath = "/Games/VoxelGame/";

		private const string WorldPersistentTempFilePrefix = "temp";

		private const string WorldFileExtension = "dat";

		private const string WorldEmbeddedFileExtension = "bytes";

		private static int _visibleX = 24;

		private static int _visibleY = 4;

		private static int _visibleZ = 24;

		private int _worldVisibleSizeX;

		private int _worldVisibleSizeY;

		private int _worldVisibleSizeZ;

		private readonly Chunk[][][] _chunks;

		private readonly Block _worldBoundBlock = new Block(BlockType.WorldBound);

		private readonly Block _unknownBlock = new Block(BlockType.Unknown);

		private Thread _loadThread;

		private readonly object _loadThreadLock = new object();

		private byte[] _worldInBytes;

		public const int _sizeOfBlockInBytes = 2;

		private readonly string _id;

		private readonly ResourceAsset _worldAsset;

		public static int VisibleX => _visibleX;

		public static int VisibleY => _visibleY;

		public static int VisibleZ => _visibleZ;

		public int WorldVisibleSizeX => _worldVisibleSizeX;

		public int WorldVisibleSizeY => _worldVisibleSizeY;

		public int WorldVisibleSizeZ => _worldVisibleSizeZ;

		public Chunk[][][] Chunks => _chunks;

		public byte[] WorldInBytes => _worldInBytes;

		public Transform WorldTransform
		{
			get;
			set;
		}

		public Block this[int wx, int wy, int wz]
		{
			get
			{
				if (wx < 0 || wx >= _worldVisibleSizeX || wy < 0 || wy >= _worldVisibleSizeY || wz < 0 || wz >= _worldVisibleSizeZ)
				{
					return _worldBoundBlock;
				}
				int num = wx >> 3;
				if (num < 0 || num >= _visibleX)
				{
					return _unknownBlock;
				}
				int num2 = wy >> 3;
				if (num2 < 0 || num2 >= _visibleY)
				{
					return _unknownBlock;
				}
				int num3 = wz >> 3;
				if (num3 < 0 || num3 >= _visibleZ)
				{
					return _unknownBlock;
				}
				Chunk chunk = _chunks[num][num2][num3];
				int lx = wx & 7;
				int ly = wy & 7;
				int lz = wz & 7;
				return chunk[lx, ly, lz];
			}
			set
			{
				int num = wx >> 3;
				if (num < 0 || num >= _visibleX)
				{
					return;
				}
				int num2 = wy >> 3;
				if (num2 >= 0 && num2 < _visibleY)
				{
					int num3 = wz >> 3;
					if (num3 >= 0 && num3 < _visibleZ)
					{
						int lx = wx & 7;
						int ly = wy & 7;
						int lz = wz & 7;
						Chunk chunk = _chunks[num][num2][num3];
						chunk[lx, ly, lz] = value;
						int num4 = wx + wy * _worldVisibleSizeX + wz * _worldVisibleSizeY * _worldVisibleSizeX;
						_worldInBytes[num4 * 2] = value._byteType;
						_worldInBytes[num4 * 2 + 1] = value.Light;
					}
				}
			}
		}

		public World(string id, ResourceAsset worldAsset, Vector3i visible)
		{
			if (string.IsNullOrEmpty(id))
			{
				id = "default";
			}
			_visibleX = visible.x;
			_visibleY = visible.y;
			_visibleZ = visible.z;
			_chunks = new Chunk[_visibleX][][];
			for (int i = 0; i < _visibleX; i++)
			{
				_chunks[i] = new Chunk[_visibleY][];
				for (int j = 0; j < _visibleY; j++)
				{
					_chunks[i][j] = new Chunk[_visibleZ];
					for (int k = 0; k < _visibleZ; k++)
					{
						_chunks[i][j][k] = new Chunk(this, i, j, k);
					}
				}
			}
			_worldVisibleSizeX = _visibleX * 8;
			_worldVisibleSizeY = _visibleY * 8;
			_worldVisibleSizeZ = _visibleZ * 8;
			_worldInBytes = new byte[_worldVisibleSizeX * _worldVisibleSizeY * _worldVisibleSizeZ * 2];
			_id = id;
			_worldAsset = worldAsset;
			if (_worldAsset == null)
			{
				ChunkRenderer.WorldHeight = new float[_worldVisibleSizeX, _worldVisibleSizeZ];
			}
		}

		public void CreateChunkMesh(int x, int y, int z, ChunkObject chunkObject)
		{
			ChunkRenderer.Render(_chunks[x][y][z], chunkObject);
			_chunks[x][y][z].ChunkObject = chunkObject;
		}

		public void RefreshChunkMesh(Block block, Action complete)
		{
			if (block != null)
			{
				Vector3i vector3i = default(Vector3i);
				vector3i.x = block._wx;
				vector3i.y = block._wy;
				vector3i.z = block._wz;
				ChunkRenderer.UpdateHeightMap(this, vector3i);
				ChunkRenderer.LightningFloodArea(this, vector3i);
				RefreshNeighborChunksIfNeeded(vector3i);
				complete.Fire();
			}
		}

		public void RefreshAreaMesh(Vector3i centerWorldPosition, int radius, Action complete)
		{
			ChunkRenderer.UpdateHeightMap(this, centerWorldPosition, radius);
			ChunkRenderer.LightningFloodArea(this, centerWorldPosition, radius);
			Vector3i vector3i = centerWorldPosition;
			vector3i.x -= radius;
			vector3i.y -= radius;
			vector3i.z -= radius;
			Vector3i vector3i2 = centerWorldPosition;
			vector3i2.x += radius;
			vector3i2.y += radius;
			vector3i2.z += radius;
			Vector3i worldPosition = default(Vector3i);
			for (int i = vector3i.x; i <= vector3i2.x; i++)
			{
				for (int j = vector3i.y; j <= vector3i2.y; j++)
				{
					for (int k = vector3i.z; k <= vector3i2.z; k++)
					{
						worldPosition.x = i;
						worldPosition.y = j;
						worldPosition.z = k;
						RefreshNeighborChunksIfNeeded(worldPosition);
					}
				}
			}
			complete.Fire();
		}

		public void RefreshNeighborChunksIfNeeded(Vector3i worldPosition)
		{
			Vector3i vector3i = WorldToChunkPosition(worldPosition);
			Vector3i chunkIndex = GetChunkIndex(worldPosition);
			RefreshChunkMeshByIndex(chunkIndex);
			if (vector3i.y >= 7)
			{
				Vector3i chunkIndex2 = chunkIndex;
				chunkIndex2.y++;
				RefreshChunkMeshByIndex(chunkIndex2);
			}
			if (vector3i.y <= 0)
			{
				Vector3i chunkIndex3 = chunkIndex;
				chunkIndex3.y--;
				RefreshChunkMeshByIndex(chunkIndex3);
			}
			if (vector3i.x >= 7)
			{
				Vector3i chunkIndex4 = chunkIndex;
				chunkIndex4.x++;
				RefreshChunkMeshByIndex(chunkIndex4);
			}
			if (vector3i.x <= 0)
			{
				Vector3i chunkIndex5 = chunkIndex;
				chunkIndex5.x--;
				RefreshChunkMeshByIndex(chunkIndex5);
			}
			if (vector3i.z >= 7)
			{
				Vector3i chunkIndex6 = chunkIndex;
				chunkIndex6.z++;
				RefreshChunkMeshByIndex(chunkIndex6);
			}
			if (vector3i.z <= 0)
			{
				Vector3i chunkIndex7 = chunkIndex;
				chunkIndex7.z--;
				RefreshChunkMeshByIndex(chunkIndex7);
			}
		}

		private void RefreshChunkMeshByIndex(Vector3i chunkIndex)
		{
			int x = chunkIndex.x;
			int y = chunkIndex.y;
			int z = chunkIndex.z;
			if (x >= 0 && x < _visibleX && y >= 0 && y < _visibleY && z >= 0 && z < _visibleZ)
			{
				_chunks[x][y][z].SetDirty();
			}
		}

		private Vector3i WorldToChunkPosition(Vector3i worldPos)
		{
			Vector3i chunkIndex = GetChunkIndex(worldPos);
			chunkIndex.x = (worldPos.x & 7);
			chunkIndex.y = (worldPos.y & 7);
			chunkIndex.z = (worldPos.z & 7);
			return chunkIndex;
		}

		private Vector3i GetChunkIndex(Vector3i worldPos)
		{
			int x = worldPos.x;
			int y = worldPos.y;
			int z = worldPos.z;
			if (x < 0 || y < 0 || z < 0)
			{
				return Vector3i.zero;
			}
			if (x >= _worldVisibleSizeX || y >= _worldVisibleSizeY || z >= _worldVisibleSizeZ)
			{
				return Vector3i.zero;
			}
			int num = x >> 3;
			int num2 = y >> 3;
			int num3 = z >> 3;
			if (num < 0 || num2 < 0 || num3 < 0)
			{
				return Vector3i.zero;
			}
			if (num >= _visibleX || num2 >= _visibleY || num3 >= _visibleZ)
			{
				return Vector3i.zero;
			}
			Vector3i result = default(Vector3i);
			result.x = num;
			result.y = num2;
			result.z = num3;
			return result;
		}

		private byte[] ToByteArray()
		{
			byte[] array = new byte[_worldVisibleSizeX * _worldVisibleSizeY * _worldVisibleSizeZ * 2];
			for (int i = 0; i < _worldVisibleSizeZ; i++)
			{
				for (int j = 0; j < _worldVisibleSizeY; j++)
				{
					for (int k = 0; k < _worldVisibleSizeX; k++)
					{
						Block block = this[k, j, i];
						byte byteType = block._byteType;
						byte light = block.Light;
						int num = k + j * _worldVisibleSizeX + i * _worldVisibleSizeY * _worldVisibleSizeX;
						int num2 = num * 2;
						array[num2] = byteType;
						array[num2 + 1] = light;
					}
				}
			}
			return array;
		}

		public void UpdateBlockInWorldBytes(Block block)
		{
			int num = block._wx + block._wy * _worldVisibleSizeX + block._wz * _worldVisibleSizeY * _worldVisibleSizeX;
			int num2 = num * 2;
			_worldInBytes[num2] = block._byteType;
			_worldInBytes[num2 + 1] = block.Light;
		}

		public void Delete()
		{
			string str = DirectoryPath();
			string str2 = string.Format("{0}.{1}", _id, "dat");
			string path = str + str2;
			if (File.Exists(path))
			{
				File.Delete(path);
			}
		}

		public void SaveSync(bool useDataExtension = true, bool isWorldEditor = false)
		{
			Stopwatch stopwatch = new Stopwatch();
			stopwatch.Start();
			WorldSaveFileController.SaveWorld(_id, _worldInBytes, WorldVisibleSizeX, WorldVisibleSizeY, WorldVisibleSizeZ, useDataExtension, isWorldEditor);
			stopwatch.Stop();
			UnityEngine.Debug.Log("[Voxel] World Saved in: " + stopwatch.ElapsedMilliseconds + " ms");
		}

		private string DirectoryPath()
		{
			return Application.persistentDataPath + "/Games/VoxelGame/";
		}

		public void LoadAsync(Action<bool> complete)
		{
			string directoryPath = Application.persistentDataPath + "/Games/VoxelGame/";
			string fileName = string.Format("{0}.{1}", _id, "dat");
			_loadThread = new Thread((ThreadStart)delegate
			{
				lock (_loadThreadLock)
				{
					try
					{
						Load(directoryPath, fileName);
						complete.Fire(arg: true);
					}
					catch (WorldLoadException)
					{
						complete.Fire(arg: false);
					}
				}
			});
			_loadThread.Start();
		}

		private void Load(string directoryPath, string fileName)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(directoryPath + fileName);
				using (FileStream stream = fileInfo.OpenRead())
				{
					LoadFromStream(stream);
				}
			}
			catch (Exception ex)
			{
				throw new WorldLoadException(ex.Message);
			}
		}

		private void LoadFromStream(Stream stream)
		{
			byte[] array = new byte[4];
			byte[] array2 = new byte[4];
			byte[] array3 = new byte[4];
			stream.Read(array, 0, 4);
			stream.Read(array2, 0, 4);
			stream.Read(array3, 0, 4);
			_worldVisibleSizeX = BitConverter.ToInt32(array, 0);
			_worldVisibleSizeY = BitConverter.ToInt32(array2, 0);
			_worldVisibleSizeZ = BitConverter.ToInt32(array3, 0);
			_worldInBytes = new byte[_worldVisibleSizeX * _worldVisibleSizeY * _worldVisibleSizeZ * 2];
			stream.Read(_worldInBytes, 0, _worldInBytes.Length);
		}

		public IEnumerator LoadFromAssetBundle(MonoBehaviour starter, Action<bool> complete)
		{
			TextAsset worldData = null;
			yield return starter.StartCoroutine(_worldAsset.AsyncAsset(delegate(TextAsset resolvedWorldData)
			{
				worldData = resolvedWorldData;
			}));
			if (worldData == null)
			{
				complete.Fire(arg: false);
			}
			else
			{
				using (MemoryStream stream = new MemoryStream(worldData.bytes))
				{
					try
					{
						LoadFromStream(stream);
						complete.Fire(arg: true);
					}
					catch (Exception)
					{
						complete.Fire(arg: false);
					}
				}
			}
		}
	}
}
